use indexmap::IndexMap;

use crate::{
    api::{ApiData, VersionData},
    utils::{
        analyze::{
            analyze_type, class_is_stack_allocated, has_recursive_destructor, is_primitive_arg,
            search_for_class_by_class_name,
        },
        string::snake_case_to_lower_camel,
    },
};

const PREFIX: &str = "Az";

/// Generate Rust DLL code from API data
pub fn generate_rust_dll(api_data: &ApiData, version: &str) -> String {
    let mut code = String::new();

    // Get the latest version
    let version_data = api_data.get_version(version).unwrap();

    code.push_str(&format!(
        "//! WARNING: autogenerated code for azul api version {version}\r\n"
    ));
    code.push_str("\r\n");
    code.push_str("#![deny(improper_ctypes_definitions)]\r\n");
    code.push_str("\r\n");

    // Include header from template file - in a real implementation you'd read this from a file
    code.push_str("// Header would be included here from _patches/dll/header.rs\r\n\r\n");

    code.push_str("pub mod widgets;\r\n");
    code.push_str(
        "#[cfg(all(feature = \"python-extension\", feature = \"link-dynamic\", not(feature = \
         \"link-static\")))]\r\n",
    );
    code.push_str("pub mod python;\r\n");
    code.push_str("\r\n");

    let mut structs_map = IndexMap::new();
    let mut rust_functions_map = IndexMap::new();

    // Primitive types that should never get an Az prefix
    const PRIMITIVE_TYPES: &[&str] = &[
        "bool", "f32", "f64", "fn", "i128", "i16", "i32", "i64", "i8", "isize", 
        "slice", "u128", "u16", "u32", "u64", "u8", "usize", "c_void",
        "str", "char", "c_char", "c_schar", "c_uchar",
    ];

    // Single-letter types are usually generic type parameters
    fn is_generic_type_param(type_name: &str) -> bool {
        type_name.len() == 1 && type_name.chars().next().map(|c| c.is_ascii_uppercase()).unwrap_or(false)
    }

    // Process all modules and classes
    for (module_name, module) in &version_data.api {
        eprintln!(
            "DEBUG: Processing module: {} ({} classes)",
            module_name,
            module.classes.len()
        );

        for (class_name, class_data) in &module.classes {
            // Skip primitive types and generic type parameters
            if PRIMITIVE_TYPES.contains(&class_name.as_str()) || is_generic_type_param(class_name) {
                continue;
            }

            // Debug print for LayoutZIndexValue
            if class_name == "LayoutZIndexValue" {
                eprintln!("DEBUG: Found LayoutZIndexValue");
                eprintln!("  external: {:?}", class_data.external);
                eprintln!("  doc: {:?}", class_data.doc);
                eprintln!("  type_alias: {:?}", class_data.type_alias);
            }

            code.push_str("\r\n");

            // Handle type aliases (generic type instantiations)
            if let Some(type_alias_info) = &class_data.type_alias {
                eprintln!("DEBUG: Generating type alias for {}", class_name);

                // Always add prefix, even if type already has it (consistency)
                let class_ptr_name = format!("{}{}", PREFIX, class_name);

                // Add documentation
                if let Some(doc) = &class_data.doc {
                    for line in doc {
                        code.push_str(&format!("/// {}\r\n", line));
                    }
                }

                // Generate type alias: pub type Az1LayoutZIndexValue =
                // Az1CssPropertyValue<Az1LayoutZIndex>;
                // Always add prefix, even if type already has it (consistency)
                let target_with_prefix = format!("{}{}", PREFIX, type_alias_info.target);
                let args_with_prefix: Vec<String> = type_alias_info
                    .generic_args
                    .iter()
                    .map(|arg| format!("{}{}", PREFIX, arg))
                    .collect();

                code.push_str(&format!(
                    "pub type {} = {}<{}>;\r\n",
                    class_ptr_name,
                    target_with_prefix,
                    args_with_prefix.join(", ")
                ));

                continue; // Skip normal class generation for type aliases
            }

            // Determine class characteristics
            let class_is_boxed_object = !class_is_stack_allocated(class_data);
            let class_is_const = class_data.const_value_type.is_some();
            let class_can_be_cloned = class_data.clone.unwrap_or(true);

            let default_vec = Vec::new();
            let derive_attrs = class_data.derive.as_ref().unwrap_or(&default_vec);
            let class_can_derive_debug = derive_attrs.contains(&"Debug".to_string());
            let class_can_be_copied = derive_attrs.contains(&"Copy".to_string());
            let class_has_partialeq = derive_attrs.contains(&"PartialEq".to_string());
            let class_has_eq = derive_attrs.contains(&"Eq".to_string());
            let class_has_partialord = derive_attrs.contains(&"PartialOrd".to_string());
            let class_has_ord = derive_attrs.contains(&"Ord".to_string());
            let class_can_be_hashed = derive_attrs.contains(&"Hash".to_string());

            let class_has_custom_destructor = class_data.custom_destructor.unwrap_or(false);
            let class_is_callback_typedef = class_data.callback_typedef.is_some();
            let is_boxed_object = class_data.is_boxed_object;
            let treat_external_as_ptr = class_data.external.is_some() && is_boxed_object;

            // Stack-allocated structs/enums don't have destructors
            let c_is_stack_allocated = !class_is_boxed_object;

            // Always add prefix, even if type already has it (consistency)
            let class_ptr_name = format!("{}{}", PREFIX, class_name);

            // Add class documentation
            if let Some(doc) = &class_data.doc {
                for line in doc {
                    code.push_str(&format!("/// {}\r\n", line));
                }
            } else {
                if c_is_stack_allocated {
                    code.push_str(&format!(
                        "/// Re-export of rust-allocated (stack based) `{}` struct\r\n",
                        class_name
                    ));
                } else {
                    code.push_str(&format!(
                        "/// Pointer to rust-allocated `Box<{}>` struct\r\n",
                        class_name
                    ));
                }
            }

            // Generate class definition
            if let Some(external) = &class_data.external {
                if class_is_const {
                    if let Some(const_val) = &class_data.const_value_type {
                        code.push_str(&format!(
                            "pub static {}: {}{} = {};\r\n",
                            class_ptr_name, PREFIX, const_val, external
                        ));
                    }
                } else if class_is_boxed_object {
                    if let Some(callback_typedef) = &class_data.callback_typedef {
                        // Add callback typedef
                        code.push_str(&format!(
                            "pub type {} = {};\r\n",
                            class_ptr_name, "/* callback signature would go here */"
                        ));
                        structs_map.insert(class_ptr_name.clone(), class_data);
                    } else {
                        // Add struct for boxed object
                        structs_map.insert(class_ptr_name.clone(), class_data);

                        if treat_external_as_ptr {
                            code.push_str(&format!(
                                "pub use {} as {}TT;\r\n",
                                external, class_ptr_name
                            ));
                            code.push_str(&format!(
                                "pub use {}TT as {};\r\n",
                                class_ptr_name, class_ptr_name
                            ));
                        } else {
                            code.push_str(&format!(
                                "#[repr(C)] pub struct {} {{ pub ptr: *mut c_void }}\r\n",
                                class_ptr_name
                            ));
                        }
                    }
                } else {
                    // Handle stack-allocated structs and enums
                    if class_data.struct_fields.is_some() {
                        structs_map.insert(class_ptr_name.clone(), class_data);
                    } else if class_data.enum_fields.is_some() {
                        structs_map.insert(class_ptr_name.clone(), class_data);
                    }

                    code.push_str(&format!(
                        "pub use {} as {}TT;\r\n",
                        external, class_ptr_name
                    ));
                    code.push_str(&format!(
                        "pub use {}TT as {};\r\n",
                        class_ptr_name, class_ptr_name
                    ));
                }
            } else {
                code.push_str("/* Error: 'external' key is required for this class */\r\n");
            }

            // Generate constructors
            if let Some(constructors) = &class_data.constructors {
                for (fn_name, constructor) in constructors {
                    if let Some(fn_body) = &constructor.fn_body {
                        let mut body = String::new();

                        if c_is_stack_allocated {
                            body = fn_body.clone();
                        } else {
                            body = format!("let object: {} = {}; ", class_name, fn_body);
                            body.push_str(&format!(
                                "let ptr = Box::into_raw(Box::new(object)) as *mut c_void; "
                            ));
                            body.push_str(&format!("{} {{ ptr }}", class_ptr_name));
                        }

                        // Add constructor documentation
                        if let Some(doc) = &constructor.doc {
                            for line in doc {
                                code.push_str(&format!("/// {}\r\n", line));
                            }
                        } else {
                            code.push_str(&format!(
                                "/// Creates a new `{}` instance whose memory is owned by the \
                                 rust allocator\r\n",
                                class_name
                            ));
                            code.push_str(&format!(
                                "/// Equivalent to the Rust `{}::{}()` constructor.\r\n",
                                class_name, fn_name
                            ));
                        }

                        // Determine return type
                        let mut returns = class_ptr_name.clone();
                        if let Some(return_info) = &constructor.returns {
                            let return_type = &return_info.r#type;
                            let (prefix, type_name, suffix) = analyze_type(return_type);

                            if is_primitive_arg(&type_name) {
                                returns = return_type.clone();
                            } else if let Some((_, return_class)) =
                                search_for_class_by_class_name(version_data, &type_name)
                            {
                                returns = format!("{}{}{}{}", prefix, PREFIX, return_class, suffix);
                            }
                        }

                        // Generate function arguments
                        let fn_args = fn_args_c_api(
                            constructor,
                            class_name,
                            &class_ptr_name,
                            false,
                            version_data,
                        );

                        // Store function signature in map
                        rust_functions_map.insert(
                            format!("{}_{}", class_ptr_name, snake_case_to_lower_camel(fn_name)),
                            (fn_args.clone(), returns.clone()),
                        );

                        // Generate constructor function
                        code.push_str(&format!(
                            "#[no_mangle] pub extern \"C\" fn {}_{} ({}) -> {} {{ {} }}\r\n",
                            class_ptr_name,
                            snake_case_to_lower_camel(fn_name),
                            fn_args,
                            returns,
                            body
                        ));
                    }
                }
            }

            // Generate methods
            if let Some(functions) = &class_data.functions {
                for (fn_name, function) in functions {
                    if let Some(fn_body) = &function.fn_body {
                        // Add function documentation
                        if let Some(doc) = &function.doc {
                            for line in doc {
                                code.push_str(&format!("/// {}\r\n", line));
                            }
                        } else {
                            code.push_str(&format!(
                                "/// Equivalent to the Rust `{}::{}()` function.\r\n",
                                class_name, fn_name
                            ));
                        }

                        // Generate function arguments
                        let fn_args = fn_args_c_api(
                            function,
                            class_name,
                            &class_ptr_name,
                            true,
                            version_data,
                        );

                        // Determine return type
                        let mut returns = String::new();
                        if let Some(return_info) = &function.returns {
                            let return_type = &return_info.r#type;
                            let (prefix, type_name, suffix) = analyze_type(return_type);

                            if is_primitive_arg(&type_name) {
                                returns = return_type.clone();
                            } else if let Some((_, return_class)) =
                                search_for_class_by_class_name(version_data, &type_name)
                            {
                                returns = format!("{}{}{}{}", prefix, PREFIX, return_class, suffix);
                            }
                        }

                        // Store function signature in map
                        rust_functions_map.insert(
                            format!("{}_{}", class_ptr_name, snake_case_to_lower_camel(fn_name)),
                            (fn_args.clone(), returns.clone()),
                        );

                        // Generate function
                        let return_arrow = if returns.is_empty() { "" } else { " -> " };
                        code.push_str(&format!(
                            "#[no_mangle] pub extern \"C\" fn {}_{} ({}){}{}{{ {} }}\r\n",
                            class_ptr_name,
                            snake_case_to_lower_camel(fn_name),
                            fn_args,
                            return_arrow,
                            returns,
                            fn_body
                        ));
                    }
                }
            }

            // Generate destructor and deep copy methods
            if c_is_stack_allocated {
                if class_can_be_copied {
                    // No destructor needed for copyable types
                } else if class_has_custom_destructor
                    || treat_external_as_ptr
                    || has_recursive_destructor(version_data, class_data)
                {
                    // Generate destructor
                    code.push_str(&format!(
                        "/// Destructor: Takes ownership of the `{}` pointer and deletes it.\r\n",
                        class_name
                    ));

                    rust_functions_map.insert(
                        format!("{}_delete", class_ptr_name),
                        (format!("object: &mut {}", class_ptr_name), String::new()),
                    );

                    code.push_str(&format!(
                        "#[no_mangle] pub extern \"C\" fn {}_delete(object: &mut {}) {{ ",
                        class_ptr_name, class_ptr_name
                    ));

                    if is_boxed_object {
                        code.push_str(
                            " if object.run_destructor { unsafe { \
                             core::ptr::drop_in_place(object); } }",
                        );
                    } else {
                        code.push_str(" unsafe { core::ptr::drop_in_place(object); } ");
                    }

                    code.push_str("}\r\n");
                }

                if treat_external_as_ptr && class_can_be_cloned {
                    // Generate deep copy method
                    code.push_str("/// Clones the object\r\n");

                    rust_functions_map.insert(
                        format!("{}_deepCopy", class_ptr_name),
                        (
                            format!("object: &{}", class_ptr_name),
                            class_ptr_name.clone(),
                        ),
                    );

                    code.push_str(&format!(
                        "#[no_mangle] pub extern \"C\" fn {}_deepCopy(object: &{}) -> {} {{ ",
                        class_ptr_name, class_ptr_name, class_ptr_name
                    ));
                    code.push_str("object.clone()");
                    code.push_str(" }\r\n");
                }
            } else {
                code.push_str("/* Error: Type is not stack allocated! */\r\n");
            }
        }
    }

    // Add size test code
    code.push_str("\r\n\r\n");
    code.push_str(&generate_size_test(api_data, &structs_map));

    code
}

/// Generate size test code
pub fn generate_size_test(
    api_data: &ApiData,
    structs_map: &IndexMap<String, &crate::api::ClassData>,
) -> String {
    let mut test_str = String::new();

    test_str.push_str("#[cfg(all(test, not(feature = \"rlib\")))]\r\n");
    test_str.push_str("#[allow(dead_code)]\r\n");
    test_str.push_str("mod test_sizes {\r\n");

    // Add test sizes header
    test_str.push_str(include_str!("./dll-patch/test-sizes.rs"));
    test_str.push_str("\r\n\r\n");

    // Generate structs
    test_str.push_str("    // Generated structs would go here\r\n");

    test_str.push_str("    use core::ffi::c_void;\r\n");
    test_str.push_str("    use azul_impl::css::*;\r\n");
    test_str.push_str("\r\n");

    test_str.push_str("    #[test]\r\n");
    test_str.push_str("    fn test_size() {\r\n");
    test_str.push_str("         use core::alloc::Layout;\r\n");

    for (struct_name, class_data) in structs_map {
        if let Some(external) = &class_data.external {
            test_str.push_str(&format!(
                "        assert_eq!((Layout::new::<{}>(), \"{}\"), (Layout::new::<{}>(), \
                 \"{}\"));\r\n",
                external, struct_name, struct_name, struct_name
            ));
        }
    }

    test_str.push_str("    }\r\n");
    test_str.push_str("}\r\n");

    test_str
}

/// Generate function arguments for C API
fn fn_args_c_api(
    function: &crate::api::FunctionData,
    class_name: &str,
    class_ptr_name: &str,
    self_as_first_arg: bool,
    version_data: &VersionData,
) -> String {
    let mut fn_args = String::new();

    if self_as_first_arg {
        if let Some(first_arg) = function.fn_args.get(0) {
            if let Some((_, self_val)) = first_arg.iter().next() {
                if self_val == "value" {
                    fn_args.push_str(&format!(
                        "{}: {}, ",
                        class_name.to_lowercase(),
                        class_ptr_name
                    ));
                } else if self_val == "mut value" {
                    fn_args.push_str(&format!(
                        "mut {}: {}, ",
                        class_name.to_lowercase(),
                        class_ptr_name
                    ));
                } else if self_val == "refmut" {
                    fn_args.push_str(&format!(
                        "{}: &mut {}, ",
                        class_name.to_lowercase(),
                        class_ptr_name
                    ));
                } else if self_val == "ref" {
                    fn_args.push_str(&format!(
                        "{}: &{}, ",
                        class_name.to_lowercase(),
                        class_ptr_name
                    ));
                } else {
                    // Error: Wrong self value type
                }
            }
        }
    }

    for arg_object in &function.fn_args {
        for (arg_name, arg_type) in arg_object {
            if arg_name == "self" {
                continue;
            }

            let (prefix, type_name, suffix) = analyze_type(arg_type);

            if is_primitive_arg(&type_name) {
                fn_args.push_str(&format!(
                    "{}: {}{}{}, ",
                    arg_name, prefix, type_name, suffix
                ));
            } else if let Some((_, class_name)) =
                search_for_class_by_class_name(version_data, &type_name)
            {
                fn_args.push_str(&format!(
                    "{}: {}{}{}{}, ",
                    arg_name, prefix, PREFIX, class_name, suffix
                ));
            } else {
                // Error: Type not found
            }
        }
    }

    // Remove trailing ", " if any
    if fn_args.ends_with(", ") {
        fn_args.truncate(fn_args.len() - 2);
    }

    fn_args
}
